<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>⏰</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <canvas id="canvas" height="694">当前浏览器不支持Canvas，请更换浏览器后再试</canvas>
</body>
<script>
  /* 页面中的时间显示，通过 0，1 二维数组进行控制。 */
  let digit = [
    [
      [0, 0, 1, 1, 1, 0, 0],
      [0, 1, 1, 0, 1, 1, 0],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 1, 1, 0, 1, 1, 0],
      [0, 0, 1, 1, 1, 0, 0]
    ], //0
    [
      [0, 0, 0, 1, 1, 0, 0],
      [0, 1, 1, 1, 1, 0, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [1, 1, 1, 1, 1, 1, 1]
    ], //1
    [
      [0, 1, 1, 1, 1, 1, 0],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 1, 1, 0, 0, 0],
      [0, 1, 1, 0, 0, 0, 0],
      [1, 1, 0, 0, 0, 0, 0],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 1, 1, 1, 1, 1]
    ], //2
    [
      [1, 1, 1, 1, 1, 1, 1],
      [0, 0, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 1, 1, 1, 0, 0],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 1, 1, 1, 1, 1, 0]
    ], //3
    [
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 1, 1, 1, 0],
      [0, 0, 1, 1, 1, 1, 0],
      [0, 1, 1, 0, 1, 1, 0],
      [1, 1, 0, 0, 1, 1, 0],
      [1, 1, 1, 1, 1, 1, 1],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 1, 1, 1, 1]
    ], //4
    [
      [1, 1, 1, 1, 1, 1, 1],
      [1, 1, 0, 0, 0, 0, 0],
      [1, 1, 0, 0, 0, 0, 0],
      [1, 1, 1, 1, 1, 1, 0],
      [0, 0, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 1, 1, 1, 1, 1, 0]
    ], //5
    [
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 1, 1, 0, 0, 0],
      [0, 1, 1, 0, 0, 0, 0],
      [1, 1, 0, 0, 0, 0, 0],
      [1, 1, 0, 1, 1, 1, 0],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 1, 1, 1, 1, 1, 0]
    ], //6
    [
      [1, 1, 1, 1, 1, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 0, 1, 1, 0, 0, 0],
      [0, 0, 1, 1, 0, 0, 0],
      [0, 0, 1, 1, 0, 0, 0],
      [0, 0, 1, 1, 0, 0, 0]
    ], //7
    [
      [0, 1, 1, 1, 1, 1, 0],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 1, 1, 1, 1, 1, 0],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 1, 1, 1, 1, 1, 0]
    ], //8
    [
      [0, 1, 1, 1, 1, 1, 0],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [1, 1, 0, 0, 0, 1, 1],
      [0, 1, 1, 1, 0, 1, 1],
      [0, 0, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 0, 1, 1],
      [0, 0, 0, 0, 1, 1, 0],
      [0, 0, 0, 1, 1, 0, 0],
      [0, 1, 1, 0, 0, 0, 0]
    ], //9
    [
      [0, 0, 0, 0],
      [0, 0, 0, 0],
      [0, 1, 1, 0],
      [0, 1, 1, 0],
      [0, 0, 0, 0],
      [0, 0, 0, 0],
      [0, 1, 1, 0],
      [0, 1, 1, 0],
      [0, 0, 0, 0],
      [0, 0, 0, 0]
    ] // :
  ]

  /* 初始化一些后面需要的变量 */
  let WINDOW_WIDTH = document.body.scrollWidth
  let WINDOW_HEIGHT = document.body.scrollHeight
  let RADIUS = 8
  let MARGIN_TOP = 1
  let MARGIN_LEFT = 250

  let curShowTimeSeconds = 0

  let balls = []
  const colors = [
    '#126bae',
    '#93b5cf',
    '#51c4d3',
    '#1ba784',
    '#207f4c',
    '#f8df72',
    '#ffc90c',
    '#f26b1f',
    '#f6dcce',
    '#ee2c79'
  ]

  /* 获取画布上下文 */
  let canvas = document.getElementById('canvas')
  let context = canvas.getContext('2d')

  /* 给画布赋值 */
  canvas.width = WINDOW_WIDTH
  canvas.height = WINDOW_HEIGHT

  /* 画布文字居中 */
  context.textAlign = 'center'

  /* 获取当前时间 */
  curShowTimeSeconds = getCurrentShowTimeSeconds()
  function getCurrentShowTimeSeconds() {
    let curTime = new Date()
    let ret = curTime.getHours() * 3600 + curTime.getMinutes() * 60 + curTime.getSeconds()

    return ret
  }

  /* 每隔50毫秒渲染更新画布 */
  setInterval(function () {
    render(context)
    update()
  }, 50)

  /* 渲染函数 */
  function render(ctx) {
    // 每一次渲染之前，我们都要将画布上的原来图案进行清空，不然会有重叠
    ctx.clearRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
    // 获取 时 分 秒
    let hours = parseInt(curShowTimeSeconds / 3600)
    let minutes = parseInt((curShowTimeSeconds - hours * 3600) / 60)
    let seconds = curShowTimeSeconds % 60
    // 时 分 秒 每一个数字我们都是分开绘制的
    renderDigit(MARGIN_LEFT, MARGIN_TOP, parseInt(hours / 10), ctx)
    renderDigit(MARGIN_LEFT + 15 * (RADIUS + 1), MARGIN_TOP, parseInt(hours % 10), ctx)
    renderDigit(MARGIN_LEFT + 30 * (RADIUS + 1), MARGIN_TOP, 10, ctx)
    renderDigit(MARGIN_LEFT + 39 * (RADIUS + 1), MARGIN_TOP, parseInt(minutes / 10), ctx)
    renderDigit(MARGIN_LEFT + 54 * (RADIUS + 1), MARGIN_TOP, parseInt(minutes % 10), ctx)
    renderDigit(MARGIN_LEFT + 69 * (RADIUS + 1), MARGIN_TOP, 10, ctx)
    renderDigit(MARGIN_LEFT + 78 * (RADIUS + 1), MARGIN_TOP, parseInt(seconds / 10), ctx)
    renderDigit(MARGIN_LEFT + 93 * (RADIUS + 1), MARGIN_TOP, parseInt(seconds % 10), ctx)

    // 这是跳动小球的绘制，我们在之前会判断时间是否发生更改，
    // 如果更改，我们会将下一次要绘制的小球放入 balls 数组中，之后就行绘制
    for (let i = 0; i < balls.length; i++) {
      ctx.fillStyle = balls[i].color

      ctx.beginPath()
      ctx.arc(balls[i].x, balls[i].y, RADIUS, 0, 2 * Math.PI, true)
      ctx.closePath()

      ctx.fill()
    }
  }

  /* 显示在画布正中央的大数字 */
  function renderDigit(x, y, num, ctx) {
    ctx.fillStyle = '#142334'
    // 我们每一个数字的组成都是一个二维数组，由这些二维数组组成三维数组，这样我们可以进行判断，
    // 如果是 1 那就是显示出来，如果是 0 我们就不进行绘制
    for (let i = 0; i < digit[num].length; i++) {
      for (let j = 0; j < digit[num][i].length; j++) {
        if (digit[num][i][j] == 1) {
          ctx.beginPath()
          // 这是 canvas 画圆的内置函数
          ctx.arc(
            x + j * 2 * (RADIUS + 1) + (RADIUS + 1),
            y + i * 2 * (RADIUS + 1) + (RADIUS + 1),
            RADIUS,
            0,
            2 * Math.PI
          )
          ctx.closePath()
          ctx.fill()
        }
      }
    }
  }

  /* 控制小球的变化 */
  function update() {
    let nextShowTimeSeconds = getCurrentShowTimeSeconds()
    // 下一个时间
    let nextHours = parseInt(nextShowTimeSeconds / 3600)
    let nextMinutes = parseInt((nextShowTimeSeconds - nextHours * 3600) / 60)
    let nextSeconds = nextShowTimeSeconds % 60
    // 当前时间
    let curHours = parseInt(curShowTimeSeconds / 3600)
    let curMinutes = parseInt((curShowTimeSeconds - curHours * 3600) / 60)
    let curSeconds = curShowTimeSeconds % 60
    // 如果二者不相等，那就证明显示的时间需要发生改变，同时要将下落的小球添加到数组中
    if (nextSeconds != curSeconds) {
      if (parseInt(curHours / 10) != parseInt(nextHours / 10)) {
        addBalls(MARGIN_LEFT + 0, MARGIN_TOP, parseInt(curHours / 10))
      }
      if (parseInt(curHours % 10) != parseInt(nextHours % 10)) {
        addBalls(MARGIN_LEFT + 15 * (RADIUS + 1), MARGIN_TOP, parseInt(curHours / 10))
      }

      if (parseInt(curMinutes / 10) != parseInt(nextMinutes / 10)) {
        addBalls(MARGIN_LEFT + 39 * (RADIUS + 1), MARGIN_TOP, parseInt(curMinutes / 10))
      }
      if (parseInt(curMinutes % 10) != parseInt(nextMinutes % 10)) {
        addBalls(MARGIN_LEFT + 54 * (RADIUS + 1), MARGIN_TOP, parseInt(curMinutes % 10))
      }

      if (parseInt(curSeconds / 10) != parseInt(nextSeconds / 10)) {
        addBalls(MARGIN_LEFT + 78 * (RADIUS + 1), MARGIN_TOP, parseInt(curSeconds / 10))
      }
      if (parseInt(curSeconds % 10) != parseInt(nextSeconds % 10)) {
        addBalls(
          MARGIN_LEFT + 93 * (RADIUS + 1),
          MARGIN_TOP,
          parseInt(nextSeconds % 10)
        )
      }

      curShowTimeSeconds = nextShowTimeSeconds
    }
    // 更新小球数组
    updateBalls()
  }

  /* 更新小球数组 */
  function updateBalls() {
    for (let i = 0; i < balls.length; i++) {
      // 给小球添加 x 方向的速度
      balls[i].x += balls[i].vx

      let c = 1.0
      // 碰撞检测
      if (balls[i].y + RADIUS + balls[i].vy >= WINDOW_HEIGHT) {
        c = (WINDOW_HEIGHT - (balls[i].y + RADIUS)) / balls[i].vy
      }
      // 给小球添加 y 方向的速度
      balls[i].y += balls[i].vy
      balls[i].vy += c * balls[i].g

      // 碰撞检测
      if (balls[i].y >= WINDOW_HEIGHT - RADIUS) {
        balls[i].y = WINDOW_HEIGHT - RADIUS
        balls[i].vy = -Math.abs(balls[i].vy) * 0.75
      }
    }
    // 清空不在页面的小球，节省性能的开销
    let cnt = 0
    for (let i = 0; i < balls.length; i++) {
      if (balls[i].x + RADIUS > 0 && balls[i].x - RADIUS < WINDOW_WIDTH) {
        balls[cnt++] = balls[i]
      }
    }
    while (balls.length > cnt) {
      balls.pop()
    }
  }

  /* 添加小球 */
  function addBalls(x, y, num) {
    for (let i = 0; i < digit[num].length; i++) {
      for (let j = 0; j < digit[num][i].length; j++) {
        if (digit[num][i][j] == 1) {
          // 给小球的添加应该有的属性，同时使用 Math.random() 使得小球更加多样性
          let aBall = {
            x: x + j * 2 * (RADIUS + 1) + (RADIUS + 1),
            y: y + i * 2 * (RADIUS + 1) + (RADIUS + 1),
            g: 1.5 + Math.random(),
            vx: Math.pow(-1, Math.ceil(Math.random() * 1000)) * 4,
            vy: -5,
            color: colors[Math.floor(Math.random() * colors.length)]
          }

          balls.push(aBall)
        }
      }
    }
  }
</script>

</html>